using System;
using System.Collections.Generic;
using System.Linq;
using Tycho.Parser.Peg;

namespace Tycho.Grammar {
    public class TermMacroBuilderCompiler : ITermMacroBuilderCompiler {
        private readonly ITermMacroBuilderFactory BuilderFactory;

        public TermMacroBuilderCompiler () : this (new TermMacroBuilderFactory ()) {}

        public TermMacroBuilderCompiler (ITermMacroBuilderFactory builderFactory) {
            BuilderFactory = builderFactory;
        }

        public ITermMacroBuilder Compile (ITerm term) {
            if (term is CompositeTerm) {
                var compositeTerm = (CompositeTerm) term;

                switch (compositeTerm.Name) {
                    case "list-term":
                        return CompileListTerm (compositeTerm);
                    case "composite-term":
                        return CompileCompositeTerm (compositeTerm);
                    case "expression":
                        return CompileNamedTerm (compositeTerm);
                }
            } else {
                return BuilderFactory.BuildConstantTermMacroBuilder (term);
            }

            throw new TermMacroBuilderCompilerException ("term " + term + " not recognised");
        }

        private ITermMacroBuilder CompileNamedTerm (CompositeTerm term) {
            var name = GetSubTerm<IdentifierTerm> (term, "name");

            return BuilderFactory.BuildNamedTermMacroBuilder (name.Name);
        }

        private ITermMacroBuilder CompileCompositeTerm (CompositeTerm term) {
            var name = GetSubTerm<IdentifierTerm> (term, "name");
            var subTerms = GetSubTerm<ListTerm> (term, "sub-terms");

            IEnumerable<CompositeTermSubTerm> compositeSubTerms = subTerms.Terms.Select (st => CompileSubTerm (st));

            return BuilderFactory.BuildCompositeTermMacroBuilder (name.Name, compositeSubTerms,
                                                                  term.SourceInformation);
        }

        private static T GetSubTerm<T> (CompositeTerm term, string subTermName) where T : class, ITerm {
            ITerm subTerm;
            if (!term.SubTerms.TryGetValue (subTermName, out subTerm)) {
                throw new TermMacroBuilderCompilerException ("expected " + term.Name + " to have " + subTermName);
            }

            var typedSubTerm = subTerm as T;
            if (typedSubTerm == null) {
                throw new TermMacroBuilderCompilerException ("expected name to be " + typeof (T));
            }
            return typedSubTerm;
        }

        private CompositeTerm ExpectCompositeTerm (ITerm term, string name) {
            var compositeTerm = term as CompositeTerm;

            if (compositeTerm == null || compositeTerm.Name != name) {
                throw new TermMacroBuilderCompilerException ("expected " + term + " to be compoiste term named " + name);
            }

            return compositeTerm;
        }

        private CompositeTermSubTerm CompileSubTerm (ITerm term) {
            CompositeTerm compositeTerm = ExpectCompositeTerm (term, "sub-term");

            var nameIdentifier = GetSubTerm<IdentifierTerm> (compositeTerm, "name");
            var subTermTerm = GetSubTerm<ITerm> (compositeTerm, "term");

            return new CompositeTermSubTerm (nameIdentifier.Name, Compile (subTermTerm));
        }

        private ITermMacroBuilder CompileListTerm (CompositeTerm term) {
            var termsList = GetSubTerm<ListTerm> (term, "terms");

            IEnumerable<ITermMacroBuilder> termBuilders = termsList.Terms.Select (t => Compile (t));

            return BuilderFactory.BuildListTermMacroBuilder (termBuilders, term.SourceInformation);
        }
    }
}